//
// Created by yyl on 2018/6/6.
//


#include "YFFmpeg.h"

YFFmpeg::YFFmpeg(CallJava *callJava1, const char *url, PlayStatus *playStatus) {
    this->playStatus = playStatus;
    this->callJava = callJava1;
    this->url = url;
    isExit = false;
    pthread_mutex_init(&init_mutex, NULL);
    pthread_mutex_init(&seek_lock, NULL);
    //  pthread_mutex_init(&stop_lock, NULL);
}

YFFmpeg::~YFFmpeg() {
    pthread_mutex_destroy(&init_mutex);
    pthread_mutex_destroy(&seek_lock);
    //  pthread_mutex_destroy(&stop_lock);
}

void *decodeFFmpeg(void *data) {
    YFFmpeg *ffmpeg = (YFFmpeg *) (data);
    ffmpeg->decodeFFmpegThread();
    pthread_exit(&ffmpeg->decodeThread);
}


void YFFmpeg::parpared() {
    pthread_create(&decodeThread, NULL, decodeFFmpeg, this);
}

int avformat_callBack(void *ctx) {
    YFFmpeg *ffmpeg = (YFFmpeg *) ctx;
    if (ffmpeg == NULL || ffmpeg->isExit || ffmpeg->playStatus->exit) {
        return AVERROR_EOF;
    }
    return 0;
}

int
YFFmpeg::getCodecContext(AVCodecParameters *avCodecParameters, AVCodecContext **avCodecContext) {
    //获取解码器
    AVCodec *avCodec = avcodec_find_decoder(
            avCodecParameters->codec_id);//根据匹配的音频流codec_id找到解码器
    if (!avCodec) {
        LOGE("can not find decoder avcodec_find_decoder url :%s", url);
        exitError();
        return -1;
    }
    //利用解码器创建解码器上下文(环境)
    *avCodecContext = avcodec_alloc_context3(avCodec);
    if (!*avCodecContext) {
        LOGE("can not avcodec_alloc_context3");
        exitError();
        return -1;
    }
    if (avcodec_parameters_to_context(*avCodecContext, avCodecParameters) < 0) {
        LOGE("can not fill avcodec_parameters_to_context");
        exitError();
        return -1;
    }
    //打开解码器
    if (avcodec_open2(*avCodecContext, avCodec, 0) != 0) {//
        LOGE("can not avcodec_open2");
        exitError();
        return -1;
    }
    return 0;
}

void YFFmpeg::decodeFFmpegThread() {
    pthread_mutex_lock(&init_mutex);
    av_register_all();//注册解码器
    avformat_network_init();//初始化网络
    mAvFormatContext = avformat_alloc_context();

    mAvFormatContext->interrupt_callback.callback = avformat_callBack;
    mAvFormatContext->interrupt_callback.opaque = this;

    if (avformat_open_input(&mAvFormatContext, url, NULL, NULL) != 0) {//打开网络流或者文件
        LOGE("can not open url :%s", url);
        exitError();
        return;
    }
    if (avformat_find_stream_info(mAvFormatContext, NULL) < 0) {//获取流信息
        LOGE("can not find stream url :%s", url);
        exitError();
        return;
    }
    //获取音频流数据
    for (int i = 0; i < mAvFormatContext->nb_streams; i++) {
        if (playStatus == NULL || playStatus->exit)break;
        if (mAvFormatContext->streams[i]->codecpar->codec_type ==
            AVMEDIA_TYPE_AUDIO) {//匹配音频流信息  其实有多个音频流  中英文等
            if (audio == NULL) {
                audio = new YAudio(playStatus, mAvFormatContext->streams[i]->codecpar->sample_rate,
                                   callJava);
                audio->streamIndex = i;
                audio->avCodecParameters = mAvFormatContext->streams[i]->codecpar;
                audio->duration = mAvFormatContext->duration / AV_TIME_BASE;
                audio->time_base = mAvFormatContext->streams[i]->time_base;
                duration = audio->duration;
            }
        } else if (mAvFormatContext->streams[i]->codecpar->codec_type ==
                   AVMEDIA_TYPE_VIDEO) {//匹配音频流信息
            if (video == NULL) {
                video = new YVideo(playStatus, callJava);
                video->streamIndex = i;
                video->avCodecParameters = mAvFormatContext->streams[i]->codecpar;
                video->time_base = mAvFormatContext->streams[i]->time_base;
                int num = mAvFormatContext->streams[i]->avg_frame_rate.num;
                int den = mAvFormatContext->streams[i]->avg_frame_rate.den;
                if (num != 0 && den != 0) {
                    int fps = num / den;//[30/1] 30帧每秒
                    video->defaultDelayTime = 1.0 / fps;
                }
            }

        }
    }
    if (audio != NULL && !playStatus->exit) {
        int result = getCodecContext(audio->avCodecParameters, &audio->avCodecContext);//取得音频解码器
        if (result != 0)return;
    }
    if (video != NULL && !playStatus->exit) {
        int result = getCodecContext(video->avCodecParameters, &video->avCodecContext);//取得视频解码器
        if (result != 0)return;
    }
    LOGI("open  audio  video");
    if (callJava != NULL) {
        if (playStatus != NULL && !playStatus->exit) {
            callJava->onCallBackEvent(THREAD_CHILD, EVENT_STATE_PARPARED, EVENT_TRUE, 0);
        } else {
            isExit = true;
        }
    }
    pthread_mutex_unlock(&init_mutex);
}

void YFFmpeg::supportMediaCodecHardware() {
    supportMediacodec = false;
    const char *codecName = ((const AVCodec *) video->avCodecContext->codec)->name;
    supportMediacodec = callJava->onCallIsSupportHardware(codecName);
    if (supportMediacodec) {
        LOGI("当前设备支持硬解码当前视频  ->1   avCodecContext->codec)->name = %s", codecName);
        if (strcasecmp(codecName, "h264") == 0) {
            bsFilter = av_bsf_get_by_name("h264_mp4toannexb");
        } else if (strcasecmp(codecName, "h265") == 0) {
            bsFilter = av_bsf_get_by_name("hevc_mp4toannexb");
        } else {
            LOGI(" 未知解码器  ");
        }
        if (bsFilter == NULL) {
            LOGI("bsFilter == NULL");
            supportMediacodec = false;
            goto end;
        }
        if (av_bsf_alloc(bsFilter, &video->abs_ctx) != 0) {
            LOGI("av_bsf_alloc  -1");
            supportMediacodec = false;
            goto end;
        }
        if (avcodec_parameters_copy(video->abs_ctx->par_in, video->avCodecParameters) < 0) {
            LOGI("avcodec_parameters_copy  -1");
            supportMediacodec = false;
            av_bsf_free(&video->abs_ctx);
            video->abs_ctx = NULL;
            goto end;
        }
        if (av_bsf_init(video->abs_ctx) != 0) {
            LOGI("av_bsf_init  -1");
            supportMediacodec = false;
            av_bsf_free(&video->abs_ctx);
            video->abs_ctx = NULL;
            goto end;
        }
        video->abs_ctx->time_base_in = video->time_base;
    }
    end:
    if (supportMediacodec) {
        supportMediacodec = callJava->onCallInitMediacodec(
                codecName,
                video->avCodecContext->width,
                video->avCodecContext->height,
                video->avCodecContext->extradata_size,
                video->avCodecContext->extradata_size,
                video->avCodecContext->extradata,
                video->avCodecContext->extradata
        );
        if (supportMediacodec) {
            video->codecType = HardwareAcceleration;
            LOGI("当前设备支持硬解码当前视频  init ->2");
        } else {
            LOGI("当前设备-----不支持硬解码当前视频 ->1");
        }
    } else {
        LOGI("当前设备-----不支持硬解码当前视频  ->2");
    }

}


void YFFmpeg::start() {
    if (audio == NULL) {
        LOGE("audio==NULL ");
        return;
    }
    if (video == NULL) {
        LOGE("video==NULL ");
        return;
    }
    video->audio = audio;
    supportMediaCodecHardware();
    audio->play();
    video->play();
    int count = 0;
    while (playStatus != NULL && !playStatus->exit) {
        if (playStatus->seek) {
            av_usleep(1000 * 100);//100毫秒
            continue;
        }
        if (audio->queue->getQueueSize() > 50) {//先解码100帧 不用全部解码
            av_usleep(1000 * 100);//100毫秒
            continue;
        }
        AVPacket *avPacket = av_packet_alloc();
        pthread_mutex_lock(&seek_lock);
        //解码获取AVpakcet要耗费一定的时间
        int resultFrame = av_read_frame(mAvFormatContext, avPacket);
        pthread_mutex_unlock(&seek_lock);

        if (resultFrame == 0 && !playStatus->exit) {//返回0读取流成功
            if (avPacket->stream_index == audio->streamIndex) {
                count++;
                //  LOGI("解码第 %d 帧  放入队列", count);
                audio->queue->putAvpacket(avPacket);
            } else if (avPacket->stream_index == video->streamIndex) {
                video->queue->putAvpacket(avPacket);
            } else {
                av_packet_free(&avPacket);
                av_free(avPacket);
                avPacket = NULL;
            }
        } else {
            av_packet_free(&avPacket);
            av_free(avPacket);
            avPacket = NULL;

            while (playStatus != NULL && !playStatus->exit) {
                if (audio->queue->getQueueSize() > 0) {
                    av_usleep(1000 * 100);//100毫秒
                    continue;
                } else {
                    if (!playStatus->seek) {
                        av_usleep(1000 * 500);
                        playStatus->exit = true;
                    }
                    break;
                }
            }
            break;
        }
    }
    if (callJava != NULL)
        callJava->onCallBackEvent(THREAD_CHILD, EVENT_STATE_COMPLETE, 0, 0);
    isExit = true;
    LOGI("解码完成")
//      test
//    while (audio->queue->getQueueSize() > 0) {
//        AVPacket *avPacket = av_packet_alloc();
//
//        audio->queue->getAvpacket(avPacket);
//
//        av_packet_free(&avPacket);
//        av_free(avPacket);
//        avPacket = NULL;
//    }
}

int YFFmpeg::changePlayState(bool isPlay) {
    if (audio != NULL) {
        audio->changePlayState(isPlay);
    }
    if (playStatus != NULL) {
        playStatus->pause = !isPlay;
    }
    return 0;
}


void YFFmpeg::release() {

    playStatus->exit = true;
    pthread_mutex_lock(&init_mutex);
    int sleepCount = 0;
    while (!isExit) {
        if (sleepCount > 1000) {
            isExit = true;
            LOGI("暂停1秒后退出的")
        }
        sleepCount++;
        LOGI("10毫秒后退出")
        av_usleep(1000 * 10);//暂停10毫秒
    }
    if (audio != NULL) {
        audio->release();
        delete (audio);
        audio = NULL;
    }
    if (video != NULL) {
        video->release();
        delete (video);
        video = NULL;
    }

    if (mAvFormatContext != NULL) {
        avformat_close_input(&mAvFormatContext);
        avformat_free_context(mAvFormatContext);
        mAvFormatContext = NULL;
    }
    if (callJava != NULL) {
        callJava = NULL;
    }
    if (playStatus != NULL) {
        playStatus = NULL;
    }
    pthread_mutex_unlock(&init_mutex);
    LOGI("YFFmpeg::release")
}


void YFFmpeg::exitError() {
    if (callJava != NULL && !isExit)
        callJava->onCallBackEvent(THREAD_CHILD, EVENT_STATE_ERROR, 0, 0);
    isExit = true;
    pthread_mutex_unlock(&init_mutex);
}

void YFFmpeg::seek(int64_t secods) {
    if (duration <= 0)return;
    if (secods >= 0 && secods <= duration) {
        playStatus->seek = true;
        pthread_mutex_lock(&seek_lock);

        int64_t rel = secods * AV_TIME_BASE;

        avformat_seek_file(mAvFormatContext, -1, INT64_MIN, rel, INT64_MAX, 0);
        if (audio != NULL) {
            audio->queue->clearAvPacket();
            audio->clock = 0;
            audio->last_time = 0;
            pthread_mutex_lock(&audio->codecMutexLock);
            avcodec_flush_buffers(audio->avCodecContext);//由于AVPacket里有多个AVFrame 清除掉缓存里还残留的 AVFrame
            pthread_mutex_unlock(&audio->codecMutexLock);
        }
        if (video != NULL) {
            video->queue->clearAvPacket();
            video->clock = 0;
            pthread_mutex_lock(&video->codecMutexLock);
            avcodec_flush_buffers(video->avCodecContext);//由于AVPacket里有多个AVFrame 清除掉缓存里还残留的 AVFrame
            pthread_mutex_unlock(&video->codecMutexLock);
        }
        pthread_mutex_unlock(&seek_lock);
        playStatus->seek = false;
    }
}

int64_t YFFmpeg::getDuration() {
    return duration;
}

void YFFmpeg::setVolume(int percent) {
    if (audio != NULL) {
        audio->setVolume(percent);
    }
}

void YFFmpeg::setMute(int mute) {
    if (audio != NULL) {
        audio->setMute(mute);
    }
}

void YFFmpeg::setSpeed(double speed) {
    if (audio != NULL) {
        audio->setSpeed(speed);
    }
}

void YFFmpeg::setPitch(double pitch) {
    if (audio != NULL) {
        audio->setPitch(pitch);
    }
}

int YFFmpeg::getSampleRate() {
    if (audio != NULL) {
        return audio->sample_rate;
    }
    return 0;
}

void YFFmpeg::startRecord(bool isRecord) {
    if (audio != NULL) {
        audio->isRecord = isRecord;
    }
}

double YFFmpeg::getCurrentTime() {
    if (audio != NULL) {
        return audio->clock;
    }
    return 0.0;
}



